Conversation
|
All contributors have signed the CLA ✍️ ✅ |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
🎉 All green!❄️ No new flaky tests detected 🎯 Code Coverage (details) 🔗 Commit SHA: 038eb97 | Docs | Datadog PR Page | Give us feedback! |
4df303f to
b539c14
Compare
Bundles Sizes Evolution
🚀 CPU Performance
🧠 Memory Performance
|
|
I have read the CLA Document and I hereby sign the CLA |
3042bfe to
98490f7
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98490f775f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
9bbedbf to
bf9a162
Compare
| const hasReplaceAll = typeof (String.prototype as any).replaceAll === 'function' | ||
| const replaceDots = hasReplaceAll | ||
| ? (str: string) => (str as string & { replaceAll: (s: string, r: string) => string }).replaceAll('.', '_') | ||
| : (str: string) => str.replace(/\./g, '_') |
There was a problem hiding this comment.
| const hasReplaceAll = typeof (String.prototype as any).replaceAll === 'function' | |
| const replaceDots = hasReplaceAll | |
| ? (str: string) => (str as string & { replaceAll: (s: string, r: string) => string }).replaceAll('.', '_') | |
| : (str: string) => str.replace(/\./g, '_') | |
| const replaceDots = (str: string) => str.replace(/\./g, '_') |
There was a problem hiding this comment.
I used this implementation for performance reasons, as I assumed that in some browsers, it might be faster to use replaceAll over replace with a regular expression. What is your reason for wanting to simplify this?
| let fn = functionCache.get(cacheKey) | ||
| if (!fn) { | ||
| // eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval | ||
| fn = new Function(...contextKeys, fnBody) as (...args: any[]) => boolean |
There was a problem hiding this comment.
issue: eval is evil, we shouldn't rely on eval if possible. Best practice is often to use CSP that block such functionality.
Is there something specific in the language that requires "inline" JavaScript code? Else we can probably parse its AST and run it using JS without actually generating JS code.
There was a problem hiding this comment.
Yeah, I know 😅 This is unfortunately the best way I've found to make this code performant. The input to new Function() is only coming from the probe definitions which are fetched from the debugger-delivery-api service in dd-source. Users of the website should not have access to manipulate this input. I previously brought this up with the security team and they have given 👍
There was a problem hiding this comment.
Background: Probes can have conditions that need to be evaluated at runtime to know if the probe should trigger or not. These conditions are defined in the probe definition in a custom DSL. This DSL needs to be translated to JavaScript so we can execute it every time the instrumented method is called. That's why it needs to be performant.
|
|
||
| declare const __BUILD_ENV__SDK_VERSION__: string | ||
|
|
||
| const DELIVERY_API_PATH = '/api/ui/debugger/probe-delivery' |
There was a problem hiding this comment.
Question: what is this route? Is this supposed to live on the customer website?
There was a problem hiding this comment.
This is only a temporary path, that will only work when running inside of web-ui. I have a follow-up PR to generalize it: #4480
I on purpose didn't include it in this PR because it was already in review by the time I made that change. If you prefer, I can merge that PR into this one?
| createTest('send debugger snapshot when instrumented function is called') | ||
| .withDebugger() | ||
| .run(async ({ intakeRegistry, flushEvents, page, browserName, servers }) => { | ||
| test.skip(browserName !== 'chromium', 'Debugger tests require Chromium') |
There was a problem hiding this comment.
question why debugger tests require Chromium?
There was a problem hiding this comment.
Honestly, I can't remember why I did this. I remember running into some issue but I'm not sure anymore. I'll try to re-enable it and see what happens.
There was a problem hiding this comment.
Seems to work fine on all browsers 👍
There was a problem hiding this comment.
I was wrong. Firefox has some issues: https://gitlab.ddbuild.io/DataDog/browser-sdk/-/jobs/1664026268
I'll investigate
There was a problem hiding this comment.
Just pushed a commit to try and increase the timeouts: 038eb97
There was a problem hiding this comment.
I wonder if this is related to the performance degradation we know exists for Firefox 🤔 (as Firefox can't optimize the functions as well as other JavaScript engines can)
8130067 to
626668e
Compare
Introduce the browser debugger SDK and probe execution pipeline so browser code can evaluate conditions, capture snapshots, and render probe messages at runtime. Add Delivery API polling plus sandbox and performance tooling to support probe delivery and testing.
Use the existing debugger service configuration when polling for probes so browser callers keep sending a valid delivery identity after the applicationId rollback. This also removes the stale applicationId init examples and test fixtures from the debugger package. Made-with: Cursor
Previously Node.js was the only one using this
Ensure we don't publish it while WIP
In the browser there's no trace or span id
Replace the hand-rolled `$dd_*` stub hooks with the actual published SDK
so the benchmark measures real per-call instrumentation overhead, not
the cost of counter-incrementing stubs. With the stubs the
`instrumented_with_probes` configuration came in at roughly the same
cost as `none`; with the real SDK it's ~1.65 µs per call, which is the
number we actually want to track.
For the measurement to be statistically sound the SDK has to be fully
ready before the warmup loop runs, otherwise V8 JIT-optimizes against
an intermediate `$dd_probes`-undefined shape and then deopts mid-flight
once probes appear. To get there:
- Inline the built debugger bundle via `addInitScript({ path })` so
`DD_DEBUGGER` is defined before the test app's script tag executes.
- Mock the SDK's same-origin probe-delivery endpoint on the perf
server, routing off the request body's `service` field so parallel
benchmark workers stay isolated.
- Gate the scenario's warmup on a `__benchmarkReady` flag that the
injector flips only after `init()` returns and (for
`instrumented_with_probes`) the first poll has populated the registry.
The probe used for `instrumented_with_probes` is a typical low-impact
`LOG_PROBE` (`captureSnapshot: false`, `snapshotsPerSecond: 1`), so
measurement stays focused on probe-lookup + sampling-check cost rather
than intake traffic. The SDK's `pollInterval` is set to one day so
re-polls can't perturb the measurement window.
Previously, the Debugger SDK set `source: 'dd_debugger'` on its init configuration so it would flow into the `ddsource` URL parameter, which forced `validateAndBuildConfiguration` to convert it back to 'browser' via a `toSdkSource` helper before storing on `Configuration.source` (used as the SDK source on RUM events). The round-trip was confusing because the same field was playing two roles: URL routing source and RUM event SDK source. Split them apart: - `InitConfiguration.source` no longer accepts 'dd_debugger'. - `computeTransportConfiguration` takes an optional `sourceOverride` parameter (typed `TransportSource`, includes 'dd_debugger') used only for URL building. The Debugger SDK passes 'dd_debugger' there. - Function overloads narrow the return type when no override is given, so `Configuration.source` is `SdkSource` without a runtime conversion. - `toSdkSource` is removed. Wire behavior is unchanged: URLs still go out with `?ddsource=dd_debugger&...&dd-evp-origin=browser`, RUM events still carry a valid `SdkSource`, and downstream `source:dd_debugger` queries keep working.
ce9f93d to
09376b1
Compare
BrowserStack Firefox can pause long enough during full-suite unit runs to miss Karma's default heartbeat and no-activity thresholds. This makes the job fail as a disconnect even though the browser may still be running the suite. Increase the BrowserStack-only limits from Karma's defaults: - browserDisconnectTimeout: 2s -> 30s - pingTimeout: 60s -> 120s - browserNoActivityTimeout: 60s -> 120s Keep the change scoped to BrowserStack so local unit runs still use the stricter base config.

Motivation
Introduce the
@datadog/browser-debuggerpackage to enable Live Debugger in browser applications. This gives frontend developers the ability to add log probes to running applications, evaluate conditions, and inspect runtime state — all without redeploying or modifying source code.Changes
New package:
packages/debuggerA new
@datadog/browser-debuggerpackage that provides the full probe execution pipeline:domain/api.ts— Core instrumentation hooks (onEntry,onReturn,onThrow) that execute probes when instrumented functions are called, including condition evaluation, snapshot capture, template message rendering, and rate limiting.domain/activeEntries.ts— Tracks per-probe execution stacks for correlating entry/return/throw events, extracted to break the dependency cycle betweenapi.tsandprobes.ts.domain/probes.ts— Probe lifecycle management (add, remove, clear) with per-probe and global snapshot rate limiting. Compiles probe conditions and template segments on registration.domain/capture.ts— Deep value capture for arguments, locals, return values, and thrown errors with configurable reference depth, collection size limits, and string length limits.domain/expression.ts— Expression compiler that parses JSON expression trees (comparisons, logical operators, member access, string operations, etc.) into executable functions.domain/condition.ts— Probe condition evaluator that compiles and caches condition expressions.domain/template.ts— Template segment compiler and evaluator for rendering dynamic probe messages with runtime context.domain/stacktrace.ts— Stack trace capture and parsing fromErrorobjects.domain/deliveryApi.ts— Polling-based probe delivery client that fetches probe updates/deletions from the Delivery API using a cursor for incremental sync.transport/startDebuggerBatch.ts— Transport layer that reuses@datadog/browser-core's batch/flush infrastructure to send debugger snapshots to the logs intake.entries/main.ts— Public API surface (datadogDebugger.init()). Exposes$dd_entry/$dd_return/$dd_throw/$dd_probeshooks onglobalThisfor instrumented code. Defines the globalDD_DEBUGGERobject.Changes to
@datadog/browser-core'dd_debugger'as a valid source in configuration and transport types, mapped to'browser'for the SDK source.computeTransportConfigurationand theBatchtype so the debugger package can create its own transport.E2E test framework and scenarios
test/e2e/scenario/debugger.scenario.ts— 7 E2E test scenarios covering: basic snapshot sending, argument/return value capture, exception capture on throw, template message evaluation with expression segments, condition evaluation (both met and not met), and RUM correlation..withDebugger()builder method tocreateTest(),DebuggerIntakeRequesttype andintakeRegistry.debuggerEventsfor asserting on debugger events, debugger page setups for CDN/bundle/npm modes, and default debugger configuration.test/apps/vanilla/app.ts— Added@datadog/browser-debuggerimport andDEBUGGER_INITsupport so debugger E2E tests work in the npm setup.Performance benchmarks
test/apps/instrumentation-overhead/— Webpack test app for measuring instrumentation overhead with instrumented vs. uninstrumented function variants.test/performance/scenarios/instrumentationOverhead.scenario.ts— Benchmark scenario that stress-tests 10M function calls to measure the overhead of debugger instrumentation hooks.test/performance/createBenchmarkTest.ts— Extended withinstrumented_no_probesandinstrumented_with_probesscenario configurations and a dedicatedinjectDebuggerfunction.Tooling
scripts/build/build-test-apps.tsto include the new test app and to use resolution paths when installing peer dependencies (needed for unpublished packages like@datadog/browser-debuggerthat only exist locally as.tgzfiles).scripts/dev-server/lib/server.tsto serve the debugger bundle.Test instructions
yarn test:unit --spec "packages/debugger/src/**/*.spec.ts"yarn test:e2e:init && yarn test:e2e -g "debugger"yarn build:apps && yarn test:performanceChecklist
Tested on staging— N/A, this is a new pre-production package not yet deployed to any environment